package ru.nsu.fit.iconveyor.conveyor.node;

import ru.nsu.fit.iconveyor.conveyor.Element;
import ru.nsu.fit.iconveyor.conveyor.exception.NodeExecutionException;

import java.util.*;

public abstract class Node extends Element {
    private Map<String, NodeConnection<?>> nodeConnectionMap = new HashMap<String, NodeConnection<?>>();
    private Map<String, Object> parameters = new HashMap<String, Object>();

    Node(String id) {
        super(id);
    }

    /**
     * Returns all input and output connections of node.
     * @return all input and output connections of node
     */
    public Collection<NodeConnection<?>> getNodeConnections() {
        return nodeConnectionMap.values();
    }

    /**
     * Returns node connection with specified name with data of specified class passing thought corresponding channel
     * @param name
     * @param <T> class of data passed though corresponding channel
     * @throws NoSuchElementException if node doesn't have connection with specified name
     * @throws ClassCastException if connection passes data of wrong class
     * @return node connection
     */
    public <T> NodeConnection<T> getNodeConnectionByName(String name) {
        if(name == null) {
            throw new IllegalArgumentException();
        }

        NodeConnection nodeConnection = nodeConnectionMap.get(name);
        if(nodeConnection == null) {
            throw new NoSuchElementException("this node doesn't have connection with name " + name);
        }

        return (NodeConnection<T>)nodeConnection;
    }

    protected void addNodeConnection(NodeConnection<?> nodeConnection) {
        if(nodeConnection == null) {
            throw new IllegalArgumentException();
        }
        nodeConnectionMap.put(nodeConnection.getName(), nodeConnection);
    }

    /**
     * Returns input node connection with specified name with data of specified class passing thought corresponding channel
     * @param name
     * @param <T> class of data passed though corresponding channel
     * @throws NoSuchElementException if node doesn't have input connection with specified name
     * @throws ClassCastException if connection passes data of wrong class
     * @return node connection
     */
    public <T> NodeConnection<T> getInputNodeConnectionByName(String name) {
        return getNodeConnectionOfTypeByName(name, NodeConnection.Type.INPUT);
    }


    /**
     * Returns output node connection with specified name with data of specified class passing thought corresponding channel
     * @param name
     * @param <T> class of data passed though corresponding channel
     * @throws NoSuchElementException if node doesn't have output connection with specified name
     * @throws ClassCastException if connection passes data of wrong class
     * @return node connection
     */
    public <T> NodeConnection<T> getOutputNodeConnectionByName(String name) {
        return getNodeConnectionOfTypeByName(name, NodeConnection.Type.OUTPUT);
    }

    public <T> NodeConnection<T> getNodeConnectionOfTypeByName(String name, NodeConnection.Type type) {
        NodeConnection<T> nodeConnection = getNodeConnectionByName(name);
        if(nodeConnection.getType() != type) {
            throw new NoSuchElementException("this node doesn't have node connection of specified type with name " + name);
        }
        return nodeConnection;
    }

    public boolean isParallelExecute() {
        return true;
    }

    public boolean isSingleExecute() {
        return false;
    }

    /**
     * Executor.
     * @param inputBundles - bundles, which was requested in method preExecute.
     * @param prop - general properties for conveyor, read only.
     * @return output resources.
     */
    public abstract Map<String, Object> execute(Map<String, Object> inputBundles, Properties prop) throws NodeExecutionException;

    /**
     * This method returns resources, which need for 'execute' method.
     * Contract = if returned resource != null, that this node will wait that anybody calls 'execute' method.
     * @param availableInputBundles - available input bundles.
     * @param  prop - general properties for conveyor, read only.
     * @return resource, which need for 'execute' method, and null if execute method isn't ready.
     */
    public abstract Map<String, Object> preExecute(Map<String, Object> availableInputBundles, Properties prop);

    public <T> void setParameter(String name, T data) {
        parameters.put(name, data);
    }

    public <T> T getParameter(String name) {
        return (T) parameters.get(name);
    }

    public Map<String, Object> getParameters() {
        return Collections.unmodifiableMap(parameters);
    }

    public abstract int getOutputToInputRatio();
}
